package cn.anecansaitin.industrialization_expansion.common.item.crafting;

import cn.anecansaitin.industrialization_expansion.ModConstant;
import cn.anecansaitin.industrialization_expansion.api.common.item.IToolModule;
import cn.anecansaitin.industrialization_expansion.common.CommonEventHooks;
import cn.anecansaitin.industrialization_expansion.common.ToolModuleType;
import cn.anecansaitin.industrialization_expansion.common.item.ItemRegister;
import cn.anecansaitin.industrialization_expansion.common.item.ModifiedTool;
import cn.anecansaitin.industrialization_expansion.common.item.ModuleTemplate;
import com.google.gson.JsonObject;
import com.ibm.icu.impl.Pair;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.minecraft.inventory.CraftingInventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.ICraftingRecipe;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.JSONUtils;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.ForgeRegistryEntry;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class ModifiedToolRecipe implements ICraftingRecipe {
    private final ResourceLocation id;
    private final ModifiedTool toolType;
    private final ItemStack toolStack;
    private final List<ItemStack> templates;
    private final List<Pair<ToolModuleType, Boolean>> necessary;
    private final List<Pair<ToolModuleType, Boolean>> optional;

    public ModifiedToolRecipe(ResourceLocation id, ModifiedTool toolType, List<ModuleDecoration> modules) {
        this.id = id;
        this.toolType = toolType;
        toolStack = new ItemStack(toolType);
        necessary = new ArrayList<>();
        optional = new ArrayList<>();
        templates = new ArrayList<>();
        modules.forEach((decoration) -> {
            for (int i = 0; i < decoration.getCount(); i++) {
                List<Pair<ToolModuleType, Boolean>> list;

                if (decoration.isNecessary())
                    list = necessary;
                else
                    list = optional;

                list.add(Pair.of(decoration.getType(), decoration.core));
                ModuleTemplate template = (ModuleTemplate) ItemRegister.MODULE_TEMPLATE.get();
                ItemStack stack = template.getDefaultInstance();
                template.setName(stack, decoration.getType());
                template.setNecessary(stack, decoration.isNecessary());
                template.setCore(stack, decoration.isCore());
                templates.add(stack);
            }
        });
    }

    public ModifiedToolRecipe(ResourceLocation id, ModifiedTool type, List<Pair<ToolModuleType, Boolean>> necessary, List<Pair<ToolModuleType, Boolean>> optional) {
        this.id = id;
        this.toolType = type;
        toolStack = new ItemStack(toolType);
        this.necessary = necessary;
        this.optional = optional;
        templates = new ArrayList<>();

        for (Pair<ToolModuleType, Boolean> pair : necessary) {
            ModuleTemplate template = (ModuleTemplate) ItemRegister.MODULE_TEMPLATE.get();
            ItemStack stack = template.getDefaultInstance();
            template.setName(stack, pair.first);
            template.setNecessary(stack, true);
            template.setCore(stack, pair.second);
            templates.add(stack);
        }

        for (Pair<ToolModuleType, Boolean> pair : optional) {
            ModuleTemplate template = (ModuleTemplate) ItemRegister.MODULE_TEMPLATE.get();
            ItemStack stack = template.getDefaultInstance();
            template.setName(stack, pair.first);
            template.setNecessary(stack, false);
            template.setCore(stack, pair.second);
            templates.add(stack);
        }
    }


    @Override
    public boolean matches(CraftingInventory inventory, @Nonnull World world) {
        boolean[] necessaryFlag = new boolean[necessary.size()];
        boolean[] optionalFlag = new boolean[optional.size()];

        //对给的物品进行检查是否满足工具的合成
        for (int i = 0; i < inventory.getContainerSize(); i++) {
            ItemStack stack = inventory.getItem(i);

            if (stack.isEmpty())
                continue;

            Item item = stack.getItem();

            //含有不是模块的物品直接返回false
            if (!(item instanceof IToolModule)) {
                return false;
            }

            IToolModule module = (IToolModule) item;
            ToolModuleType type = module.getModuleType(stack);
            boolean found = false;

            //现在必要模块列表里检索
            for (int j = 0; j < necessary.size(); j++) {
                if (necessaryFlag[j])
                    continue;

                if (necessary.get(j).first == type) {
                    necessaryFlag[j] = true;
                    found = true;
                    break;
                }
            }

            if (found)
                continue;

            //必要列表未找到，则在可选列表内检索
            for (int j = 0; j < optional.size(); j++) {
                if (optionalFlag[j])
                    continue;

                if (optional.get(j).first == type) {
                    optionalFlag[j] = true;
                    found = true;
                    break;
                }
            }

            //两个列表都没找到，说明这个模块是多余的，无法合成
            if (!found)
                return false;
        }

        //检查必要列表是否都被找到
        for (boolean b : necessaryFlag) {
            if (!b)
                return false;
        }

        return true;
    }

    @Override
    public @Nonnull ItemStack assemble(CraftingInventory inventory) {
        ArrayList<ItemStack> list = new ArrayList<>();
        IntArrayList coreFlg = new IntArrayList();
        ArrayList<ToolModuleType> coreType = new ArrayList<>();
        coreType.addAll(necessary.stream().filter((p) -> p.second).collect(ArrayList::new, (l, p) -> l.add(p.first), ArrayList::addAll));
        coreType.addAll(optional.stream().filter((p) -> p.second).collect(ArrayList::new, (l, p) -> l.add(p.first), ArrayList::addAll));
        boolean[] coreTypeFlg = new boolean[coreType.size()];

        //这里已经获得了所有核心，并且必要的在前面，可选的在后面
        for (int i = 0; i < inventory.getContainerSize(); i++) {
            ItemStack stack = inventory.getItem(i);
            Item module = stack.getItem();

            if (module instanceof IToolModule) {
                //检查是不是核心部件
                for (int j = 0, coreTypeSize = coreType.size(); j < coreTypeSize; j++) {
                    if (coreTypeFlg[j])
                        continue;

                    ToolModuleType type = coreType.get(j);

                    if (type == ((IToolModule) module).getModuleType(stack)) {
                        coreFlg.add(i);
                        coreTypeFlg[j] = true;
                        break;
                    }
                }

                list.add(stack);
            }
        }

        NonNullList<ItemStack> stacks = NonNullList.withSize(list.size(), ItemStack.EMPTY);

        for (int i = 0, stacksSize = stacks.size(); i < stacksSize; i++) {
            stacks.set(i, list.get(i));
        }

        CommonEventHooks.fireModifiedToolAssemblePreEvent(this, stacks);
        ItemStack tool = toolType.getDefaultInstance();
        return CommonEventHooks.fireModifiedToolAssemblePostEvent(this, stacks, toolType.assembleTool(tool, coreFlg, stacks, necessary.size() + optional.size()));
    }

    @Override
    public boolean canCraftInDimensions(int width, int height) {
        return width * height >= 3;
    }

    @Override
    public @Nonnull ItemStack getResultItem() {
        return toolStack;
    }

    @Override
    public @Nonnull ResourceLocation getId() {
        return id;
    }

    @Override
    public @Nonnull IRecipeSerializer<?> getSerializer() {
        return ModRecipeTypes.MODIFIED_TOOL_SERIALIZER.get();
    }

    @Override
    public boolean isSpecial() {
        return true;
    }

    public List<ItemStack> getTemplates() {
        return templates;
    }

    public static class Serializer extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<ModifiedToolRecipe> {

        @Override
        public @Nonnull ModifiedToolRecipe fromJson(@Nonnull ResourceLocation id, @Nonnull JsonObject json) {
            ModifiedTool tool;
            ArrayList<ModuleDecoration> list = new ArrayList<>();

            if (JSONUtils.isStringValue(json, "tool")) {
                String toolId = JSONUtils.getAsString(json, "tool");
                Item item = ForgeRegistries.ITEMS.getValue(new ResourceLocation(ModConstant.MOD_ID, toolId));

                if (!(item instanceof ModifiedTool))
                    throw new RuntimeException("Tool not a ModifiedTool or tool not registered: " + toolId);

                tool = (ModifiedTool) item;
            } else
                throw new RuntimeException("Undefined tool");

            if (JSONUtils.isArrayNode(json, "module"))
                JSONUtils.getAsJsonArray(json, "module").forEach(j -> list.add(ModuleDecoration.fromJson((JsonObject) j)));
            else
                throw new RuntimeException("Undefined module");

            return new ModifiedToolRecipe(id, tool, list);
        }

        @Nullable
        @Override
        public ModifiedToolRecipe fromNetwork(@Nonnull ResourceLocation id, PacketBuffer buffer) {
            ModifiedTool tool = (ModifiedTool) ForgeRegistries.ITEMS.getValue(buffer.readResourceLocation());
            int necessarySize = buffer.readInt();
            ArrayList<Pair<ToolModuleType, Boolean>> necessary = new ArrayList<>();

            for (int i = 0; i < necessarySize; i++) {
                ToolModuleType type = ToolModuleType.get(buffer.readUtf());
                necessary.add(Pair.of(type, buffer.readBoolean()));
            }

            int optionalSize = buffer.readInt();
            ArrayList<Pair<ToolModuleType, Boolean>> optional = new ArrayList<>();

            for (int i = 0; i < optionalSize; i++) {
                ToolModuleType type = ToolModuleType.get(buffer.readUtf());
                optional.add(Pair.of(type, buffer.readBoolean()));
            }

            return new ModifiedToolRecipe(id, tool, necessary, optional);
        }

        @Override
        public void toNetwork(PacketBuffer buffer, ModifiedToolRecipe recipe) {
            buffer.writeResourceLocation(Objects.requireNonNull(recipe.toolType.getRegistryName()));
            buffer.writeInt(recipe.necessary.size());

            for (int i = 0; i < recipe.necessary.size(); i++) {
                Pair<ToolModuleType, Boolean> pair = recipe.necessary.get(i);
                buffer.writeUtf(pair.first.getName());
                buffer.writeBoolean(pair.second);
            }

            buffer.writeInt(recipe.optional.size());

            for (int i = 0; i < recipe.optional.size(); i++) {
                Pair<ToolModuleType, Boolean> pair = recipe.optional.get(i);
                buffer.writeUtf(pair.first.getName());
                buffer.writeBoolean(pair.second);
            }
        }
    }

    /**
     * 用于定义配方中所需要的模块类型以及数量
     */
    public static class ModuleDecoration {
        private final ToolModuleType type;
        private final int count;
        /**
         * 设定为必要，则在合成时不可缺少，否则缺少也可合成
         */
        private final boolean necessary;
        /**
         * 设定为核心，则在其属性决定工具的工具类型、修复用物品、挖掘等级
         */
        private final boolean core;

        public ModuleDecoration(ToolModuleType type, int count, boolean necessary, boolean core) {
            this.type = type;
            this.count = count;
            this.necessary = necessary;
            this.core = core;
        }

        public ToolModuleType getType() {
            return type;
        }

        public int getCount() {
            return count;
        }

        public boolean isNecessary() {
            return necessary;
        }

        public boolean isCore() {
            return core;
        }

        public static ModuleDecoration fromJson(JsonObject json) {
            if (!JSONUtils.isStringValue(json, "type"))
                throw new RuntimeException("Module not defined module type.");

            return new ModuleDecoration(ToolModuleType.get(JSONUtils.getAsString(json, "type")), JSONUtils.getAsInt(json, "count", 1), JSONUtils.getAsBoolean(json, "necessary", false), JSONUtils.getAsBoolean(json, "core", false));
        }
    }
}
